Skip to main content

3.1 Configuration

How do I configure shit?

There are two ways to configure environment variables. One is by simply exposing these variables to the environment (duh), but take care to make sure that these variables won't simply just disappear on reboot, and to actually expose them to the container if you're using Docker (you can use -e flags to pass environment variables to the underlying container).

The other way is by configuring .env files. These env files are loaded in the following order (meaning, if you have VAR=1 in .env.production.local and VAR=2 in .env, the application will see VAR=1):

.env.production.local
.env.production
.env.local
.env

Note that non-local environment files are checked in through git, so I suggest creating .env.production.local and using that for configuring production workloads instead of just overriding whatever is in .env. And again, if you're using Docker, make sure to actually volume map these files to the inside of the containers (the directory to mount it on is /home/node).

App-specific configuration

For the most part, the comments on .env files should be sufficient, but I'll explain some of the more confusing ones here just in case.

HOMEPAGE, BASE_URL, and all the other OIDC shit

See the bit about the domain name and identity provider in the Prereqs page

Sessions

You can configure SESSION_DURATION/SESSION_SECRET. The session does not "roll over" once it expires. The session is stored in Redis, using cookie-based authentication.

Please please please make sure to change SESSION_SECRET to something ACTUALLY long and private. If you're deploying via Heroku, it should already be automatically done for you.

Rate limiting

Rate limiting is crucial to prevent abuse of the Blink server, even if you have DDoS protection. There are two classes of "users" for Blink:

  1. Internal users who are using the application from their web browser (from our integrated React frontend)
  2. API clients who are calling Blink's API directly

For class 1, we really don't need to worry much about abuse, so we keep a fairly generous global rate limit (tuned by RATE_LIMIT_SHORT_WINDOW and RATE_LIMIT_SHORT_MAX, dictating how many times someone can make a request within a given window), to provide basic protection from "bursty" workloads that might result from organic use of Blink, in order to protect the server from going down.

For class 2, we need to make sure the "blast radius" is as contained as possible, so that in case of a leaked credential, or someone trying to scrape the application, or someone trying to brute force authentication, they can only do so much (RATE_LIMIT_LONG_MAX) within a given window (RATE_LIMIT_LONG_WINDOW). Note that this "long" rate limiter only applies to /api and /auth endpoints.

Timeouts

We scrape our own links here at $CORP to fill in the blanks about any given link. We have a customizable timeout via LINK_TIMEOUT. Increase this if the link scraping is not working.

Cache

There are two ways to go about configuring CACHE_MAX_AGE:

  1. Keep it relatively short (e.g. a few hours) so that the CDN would revalidate with the Blink server relatively often.
  2. Keep it basically "infinite" (the default) so that the CDN would hold onto the cache hopefully forever.

Option 1 is better if you plan on busting the cache semi-regularly and don't want to rely on some integration to bust the cache for you (at the cost of higher server load and serving slightly stale links, should you change/delete a link).

Option 2 is better if you plan on either never "deleting" links from Blink or busting the cache manually or via an integration (which will be coming post-v1 launch).

Determining IP Addresses

Blink uses the IP address of the requester for rate limiting. However, we need the correct IP address, and it is actually not as easy as you might think, especially in cases where you are running behind a reverse proxy/CDN/load balancer/etc.

Here's how it works across different scenarios and how you can configure the PROXY_TRUST variable (which internally feeds expressjs, which in turn, feeds https://github.com/jshttp/proxy-addr); this also serves as a crash course on the mysterious X-Forwarded-For.

For more technical details, please see this post: https://adam-p.ca/blog/2022/03/x-forwarded-for.

Case 1. Directly exposed to the internet

Please, for the love of god, don't do this. But in this case, the IP address of the user would simply be the remote address of the connection that's opened to the Blink server (which is also hosting the frontend, remember), so a PROXY_TRUST=false would suffice.

Case 2. Sitting behind a reverse proxy (or two)

In this case, the connection is not being made from the server, but rather from the reverse proxy that it's making the request from. Of course, this means that we can't use the remote address of the connection (which would be the reverse proxy closest to you).

It looks like this:

Connection: User ---> ProxyA ---> ProxyB ---> ... ---> ProxyZ ----> Blink Server
IP address: 1.1.1.1 2.2.2.2 3.3.3.3 27.27.27.27

Obviously, we can't use 27.27.27.27 as the IP address, because all of the rate limit "buckets" will fall under one key - the IP address of ProxyZ!

Thankfully, HTTP proxies offer a way around this, by passing around the IP address of the client that it connected to, using an (unstandard) HTTP header called X-Forwarded-For (XFF). Typically, it appends its own IP address to the XFF header, so it will look like this:

Connection:      User ---> ProxyA ----------> ProxyB -----------------> ... ---> ProxyZ -----------------> Blink Server
IP address: 1.1.1.1 2.2.2.2 3.3.3.3 27.27.27.27
X-Forwarded-For: 1.1.1.1 1.1.1.1, 2.2.2.2 1.1.1.1,2.2.2.2,3.3.3.3 1.1.1.1,...,27.27.27.27

Which means we can simply rely on the leftmost IP address on the XFF header. This is what PROXY_TRUST=true would do.

However, there are complications: the user can send their own XFF header! So they can get the server to read a spoofed IP (and thereby get around the rate limit) by sending, say, a payload with the header X-Forwarded-For: $evil_IP, and the XFF header that Blink will see will become X-Forwarded-For: $evil_IP, 1.1.1.1, 2.2.2.2, ..., 27.27.27.27 or X-Forwarded-For: 1.1.1.1, $evil_IP, 2.2.2.2, ..., 27.27.27.27 depending on the behaviour of any of the proxies that all touch the XFF header (remember, it only typically appends its own IP address to the XFF, but the XFF header has no formal specifications)!

Furthermore, this can be even more complicated by the fact that some proxies can overwrite the XFF header or, god forbid, write another XFF header to the HTTP request! At this point, which XFF header do you trust, and even within the "trusted" XFF header, which of the IP addresses is the real one?

For this, there is no way out other than to just... test it on your own setup. See where your real IP lies within the XFF header(s), but typically (and this is considered the "ideal" behaviour), your chain of proxies will have 1. "merged" your XFF headers into one (and if not, I believe expressjs does itself - and again, it is "ideal" behaviour to trust the last HTTP header on the list), and 2. strictly appended the IP addresses so that the user's real IP address will be the rightmost "untrusted" IP on the XFF header.

note

Many popular proxies do follow this "ideal" behaviour, such as Cloudflare and Traefik. See your respective proxy's documentation or the above blog post to see the XFF behaviour specific to your setup (or better yet, experiment with a whoami).

In that case, you can simply set TRUST_PROXY to the string-delimited list of IP addresses/ranges you trust; Blink will look from right to left, and "skip" any IP addresses in the trusted IPs/ranges until it comes across the first untrusted IP address, which is the client IP in the above case (e.g. if the XFF is $evil_IP, 1.1.1.1, 2.2.2.2, ..., 27.27.27.27 and you set TRUST_PROXY=2.2.2.2,...,27.27.27.27, 1.1.1.1 will be the "real" IP).